/*
 * Scene.cpp
 *
 *  Created on: 2 Nov 2012
 *      Author: guy
 */

#include "core/Scene.h"
#include <iostream>

using namespace SolidityEngine;

Scene::Scene(const std::string& name, int width, int height):
    Object(name),
	_objects(),
	_backdrop(NULL),
	_size(width,height),
    _renderscenelist(),
    _renderscreenlist(),
    _colliderlist(),
    _renderlist_mutex()
{
    _renderlist_mutex=SDL_CreateMutex();
}

Scene::Scene(const Scene& original):
    Object(original.Name()+"(clone)"),
    _objects(),
	_backdrop(original._backdrop),
	_size(original._size)
{
    std::list<GameObject_ptr>::const_iterator iter = original._objects.begin();
    while(iter!=original._objects.end()){
        _objects.push_back(GameObject_ptr(new GameObject(*iter->get())));
        iter++;
    }
}

GameObject* Scene::AddObject(const std::string& name) {
	_objects.push_back(GameObject_ptr(new GameObject(name)));
	return _objects.back().get();
}

void Scene::AddObject(GameObject* obj) {
	_objects.push_back(GameObject_ptr(obj));
}

void Scene::SetBackdropImage(Image* img){
    _backdrop=img;
    _backdrop->SetClipRect(Rect(0,0,Screen::Width(),Screen::Height()));
}

const Image* Scene::GetBackdropImage() const{
    return _backdrop;
}

Image* Scene::GetBackdropImage(){
    return _backdrop;
}

GameObject* Scene::GetObject(int ID){
    std::list<GameObject_ptr>::iterator iter=_objects.begin();
    while(iter!=_objects.end()){
        if(iter->get()->ID()==ID)
        {
            return iter->get();
        }
        iter++;
    }
    return NULL;
};

void Scene::DeleteObject(GameObject* obj)
{
    std::list<GameObject_ptr>::iterator iter=_objects.begin();
    while(iter!=_objects.end()){
        if(iter->get()==obj){
            iter=_objects.erase(iter);
        }
        else{
            iter++;
        }
    };
};

std::list<int> Scene::GetObjectIDs(){
    std::list<int> objects;
    std::list<GameObject_ptr>::iterator iter=_objects.begin();
    while(iter!=_objects.end()){
        objects.push_back(iter->get()->ID());
        iter++;
    }
    return objects;
};


Scene* Scene::_Clone()
{
    return new Scene(*this);
}

void Scene::Update()
{

    _BuildLists();

    _DetectCollisions();

}

std::shared_ptr<SDL_Surface> Scene::GetFrame()
{
    return _RenderScene();
}

void Scene::_DetectCollisions()
{
    //Do some collision detection
    std::list<Collider*>::iterator citer=_colliderlist.begin();
    while(citer!=_colliderlist.end())
    {
        if((*citer)->Enabled()){
            std::list<Collider*>::iterator citer2=_colliderlist.begin();
            while(citer2!=_colliderlist.end()){
                if(citer!=citer2){
                    Collision_ptr coll=(*citer)->Test(*citer2);
                    if(coll){
                        //we have a collision detected.
                        //Send the collision message
                        (*citer)->GetGameObject()->Collision(coll);
                    }
                }
                citer2++;
            }
        }
        citer++;
    }
}

std::shared_ptr<SDL_Surface> Scene::_RenderScene()
{

    SDL_LockMutex(_renderlist_mutex);

    //sort renderers in order of draw index
    _renderscenelist.sort(Renderer::Compare);

    //create a new surface to render everything onto
    Uint32 rmask, gmask, bmask, amask;
    #if SDL_BYTEORDER == SDL_BIG_ENDIAN
        rmask = 0xff000000;
        gmask = 0x00ff0000;
        bmask = 0x0000ff00;
        amask = 0x000000ff;
    #else
        rmask = 0x000000ff;
        gmask = 0x0000ff00;
        bmask = 0x00ff0000;
        amask = 0xff000000;
    #endif

    std::shared_ptr<SDL_Surface> tempsurf = std::shared_ptr<SDL_Surface>(SDL_CreateRGBSurface(SDL_HWSURFACE,_size.X(),_size.Y(),32,rmask, gmask, bmask, amask),SDL_FreeSurface);

    if(_backdrop)
    {

        //Calculate the number of images needed to cover the screen in x and y
        int xnum=(int)(tempsurf->w/_backdrop->GetClipRect().Width())+1;
        int ynum=(int)(tempsurf->h/_backdrop->GetClipRect().Height())+1;

        //Tiling if the rect width of the source is less than that of the target
        if(xnum>1 || ynum>1){
            for(int j=0;j<ynum;j++){
                for(int i=0;i<xnum;i++){
                    int voffset=j*_backdrop->GetClipRect().Height();
                    int hoffset=i*_backdrop->GetClipRect().Width();
                    SDL_Rect rect;
                    rect.x=hoffset;
                    rect.y=voffset;
                    rect.w=_backdrop->GetClipRect().Width();
                    rect.h=_backdrop->GetClipRect().Height();
                    SDL_BlitSurface(_backdrop->GetSurface(),NULL,tempsurf.get(),&rect);
                }
            }
        }
        SDL_BlitSurface(_backdrop->GetSurface(),NULL,tempsurf.get(),NULL);
    }

    //If object has renderers, render using the frontmost one
    std::list<Renderer*>::iterator riter=_renderscenelist.begin();
    while(riter!=_renderscenelist.end()){
        (*riter)->Render(tempsurf.get());
        riter++;
    }

    SDL_UnlockMutex(_renderlist_mutex);

    return tempsurf;

}

void Scene::RenderScreen(SDL_Surface* surf)
{
    SDL_LockMutex(_renderlist_mutex);
    //If object has renderers, render using the frontmost one
    std::list<Renderer*>::iterator riter=_renderscreenlist.begin();
    while(riter!=_renderscreenlist.end()){
        (*riter)->Render(surf);
        riter++;
    }
    SDL_UnlockMutex(_renderlist_mutex);
}

void Scene::_BuildLists()
{
     SDL_LockMutex(_renderlist_mutex);

    //create a new list of renderers
    _renderscenelist.clear();

    //create a new list of renderers
    _renderscreenlist.clear();

    //create a list of colliders
    _colliderlist.clear();

    //For eachobject in the scene add to the renderlist
    std::list<int> obj_ids = GetObjectIDs();
    std::list<int>::iterator iter = obj_ids.begin();
    while(iter!=obj_ids.end())
    {

        GameObject* obj = GetObject(*iter);

        if(obj->isDeleted())
        {
            DeleteObject(obj);
        }
        else{
           //Call Update on the object
            obj->Update();

            //Get the objects renderers
            std::list<Renderer*> renderer_list = obj->GetComponents<Renderer>();

            //iterate through them and add them to the render list
            std::list<Renderer*>::iterator r_iter=renderer_list.begin();
            while(r_iter!=renderer_list.end())
            {
                //add it to the render list (we'll need to sort this later);
                if((*r_iter)->GetRenderTarget()==Renderer::RT_SCENE){
                    _renderscenelist.push_back(*r_iter);
                }
                if((*r_iter)->GetRenderTarget()==Renderer::RT_SCREEN){
                    _renderscreenlist.push_back(*r_iter);

                }
                r_iter++;
            }

            //Get the objects colliders
            std::list<Collider*> collider_list = obj->GetComponents<Collider>();

            //iterate and add them to the objects colliders list
            std::list<Collider*>::iterator c_iter=collider_list.begin();

            while(c_iter!=collider_list.end())
            {
                //add to the collider list
                _colliderlist.push_back(*c_iter);
                c_iter++;
            }

        }
        iter++;
    }

    SDL_UnlockMutex(_renderlist_mutex);
}
